package ch.unizh.ifi.ddis.cbr.record;

import java.net.URI;
import java.util.ArrayList;
import java.util.List;

import org.mindswap.owl.EntityFactory;
import org.mindswap.owl.OWLFactory;
import org.mindswap.owl.OWLIndividual;
import org.mindswap.owl.OWLKnowledgeBase;
import org.mindswap.owl.OWLOntology;
import org.mindswap.owls.OWLSFactory;
import org.mindswap.owls.grounding.AtomicGrounding;
import org.mindswap.owls.grounding.Grounding;
import org.mindswap.owls.process.AtomicProcess;
import org.mindswap.owls.process.CompositeProcess;
import org.mindswap.owls.process.Input;
import org.mindswap.owls.process.Output;
import org.mindswap.owls.process.Perform;
import org.mindswap.owls.process.Process;
import org.mindswap.owls.process.ProcessList;
import org.mindswap.owls.process.Result;
import org.mindswap.owls.process.Sequence;
import org.mindswap.owls.process.execution.ProcessExecutionEngine;
import org.mindswap.owls.profile.Profile;
import org.mindswap.owls.service.Service;
import org.mindswap.query.ValueMap;
import org.mindswap.utils.URIUtils;
import org.mindswap.utils.Utils;

/**
 * An example to show how service descriptions can be created on the fly, saved and executed.
 *
 * @author Evren Sirin
 */
public class CreateSequence {
    public static final URI baseURI = URI.create("http://www.example.org/BookPrice.owl#");

    OWLOntology ont;
    ExecutionTrail trail;

    public CreateSequence() {
    }

    /**
     *
     * Create a new Sequence from the processes of the given services and put them in a new
     * Service object with a automatically generated Profile. This function assumes that
     * each service in the list has exactly one input and one output (except the first and
     * last one) such that in the resulting Service the output of each service will be fed
     * as input to the next one. The first service does not have to have an input and the
     * last one does not need to have an output. The resulting service will have an input
     * (or an output) depending on this.
     *
     * @param services List of Service objects
     * @param baseURI The base URI for the generated service
     * @return The Service which is a Sequence of the given services
     */
    Service createSequenceService(List services) {
        Service service = ont.createService(URIUtils.createURI(baseURI, "TestService"));
        CompositeProcess process = ont.createCompositeProcess(URIUtils.createURI(baseURI, "TestProcess"));
        Profile profile = ont.createProfile(URIUtils.createURI(baseURI, "TestProfile"));
        Grounding grounding = ont.createGrounding(URIUtils.createURI(baseURI, "TestGrounding"));

        System.out.println(ont.getKB().getServices());

        service.setProfile(profile);
        service.setProcess(process);
        service.setGrounding(grounding);

        createSequenceProcess(process, services);
        createProfile(profile, process);

        ProcessList list = process.getComposedOf().getAllProcesses();
        for(int i = 0; i < list.size(); i++) {
            Process pc = list.processAt(i);
            if(pc instanceof AtomicProcess) {
                AtomicGrounding ag = ((AtomicProcess)pc).getGrounding();
                if( ag == null ) continue;
                grounding.addGrounding( ag );
            }
        }


        profile.setLabel(createLabel(services));
        profile.setTextDescription(profile.getLabel());

        service.setProfile(profile);
        service.setProcess(process);
        service.setGrounding(grounding);
        return service;
    }

    /**
     *
     * Create a label for the composite service based on the labels of services. Basically
     * return the string [Service1 + Service2 + ... + ServiceN] as the label
     *
     * @param services List of Servie objects
     * @return
     */
    String createLabel(List services) {
        String label = "[";

        for(int i = 0; i < services.size(); i++) {
            Service s = (Service) services.get(i);

            if(i > 0) label += " + ";

            label += s.getLabel();
        }
        label += "]";

        return label;
    }

    /**
     *
     * Create a Profile for the composite service. We only set the input and output of the profile
     * based on the process.
     *
     * @param profile
     * @param process
     * @return
     */
    Profile createProfile(Profile profile, Process process) {
        for(int i = 0; i < process.getInputs().size(); i++) {
            Input input = process.getInputs().inputAt(i);

            profile.addInput(input);
        }

        for(int i = 0; i < process.getOutputs().size(); i++) {
            Output output = process.getOutputs().outputAt(i);

            profile.addOutput(output);
        }

        return profile;
    }

    /**
     *
     * Create a Sequence process for the processes of given services. Creates the DataFlow asssuming each
     * service has one output and one intput (except first and last one).
     *
     * @param compositeProcess
     * @param grounding
     * @param services
     * @return
     */
    CompositeProcess createSequenceProcess(CompositeProcess compositeProcess, List services) {
        Sequence sequence = ont.createSequence();
        compositeProcess.setComposedOf(sequence);
        
        System.out.println(services);

        Perform[] performs = new Perform[services.size()];
        for(int i = 0; i < services.size(); i++) {
            Service s = (Service) services.get(i);
            Process p = s.getProcess();

            performs[i] = ont.createPerform();
            performs[i].setProcess(p);

            sequence.addComponent(performs[i]);

            if(i > 0) {
                Perform prevPerform = performs[i - 1];
                Input input = p.getInputs().inputAt(0);
                Output output = prevPerform.getProcess().getOutputs().outputAt(0);

                // the value of 'input' is the value of 'output' from 'prevPerform'
                performs[i].addBinding(input, prevPerform, output);
            }
        }

        Perform firstPerform = performs[0];
        Perform lastPerform = performs[services.size()-1];
        boolean createInput = firstPerform.getProcess().getInputs().size() > 0;
        boolean createOutput = lastPerform.getProcess().getOutputs().size() > 0;

        if(createInput) {
            Input input = firstPerform.getProcess().getInputs().inputAt(0);
            Input newInput = ont.createInput(URIUtils.createURI(baseURI, "TestInput"));
            newInput.setLabel(input.getLabel());
            newInput.setParamType(input.getParamType());
            newInput.setProcess(compositeProcess);

            // input of the first perform is directly read from the input of the
            // composite process
            performs[0].addBinding(input, Perform.TheParentPerform, newInput);
        }

        if(createOutput) {
            Output output = lastPerform.getProcess().getOutputs().outputAt(0);
            Output newOutput = ont.createOutput(URIUtils.createURI(baseURI, "TestOutput"));
            newOutput.setLabel(output.toPrettyString());
            newOutput.setParamType(output.getParamType());
            newOutput.setProcess(compositeProcess);

            // the output of the composite process is the output pf last process
            Result result = ont.createResult();
            result.addBinding(newOutput, lastPerform, output);

            compositeProcess.setResult(result);
        }

        return compositeProcess;
    }


    public void runTest() throws Exception {
        // create an OWL-S knowledge base
        OWLKnowledgeBase kb = OWLFactory.createKB();
//        trail.setKB(kb);

        //kb.getReader().getCache().setLocalCacheDirectory( "C:\\mindswap\\composer\\cache" );
        kb.getReader().getCache().setLocalCacheDirectory( "C:\\Hacks\\workspace\\Ontologies\\ont_cache");

        
        // create an empty ontology in this KB
        ont = kb.createOntology(baseURI);

        // create an execution engine
        ProcessExecutionEngine exec = OWLSFactory.createExecutionEngine();

        trail = new ExecutionTrail(kb, exec);

        // load two services
        Service s1 = kb.readService("http://www.mindswap.org/2004/owl-s/1.1/BookFinder.owl#");
        Service s2 = kb.readService("http://www.mindswap.org/2004/owl-s/1.1/BNPrice.owl#");

        // put the services in a list
        List services = new ArrayList();
        services.add(s1);
        services.add(s2);

        // create a new service as a sequence of the list
        Service s = createSequenceService(services);

        // print the description of new service to standard output
        ont.write(System.out, baseURI);
        System.out.println();

        // get the process of the new service
        Process process = s.getProcess();
        // initialize the input values to be empty
        ValueMap values = new ValueMap();
        // get the parameter using the local name
        values.setValue(process.getInputs().inputAt(0), EntityFactory.createDataValue("City of Glass"));

        // execute the service
        System.out.print("Executing...");
        
		exec.addMonitor(new ExecutionMonitor(trail));
        values = exec.execute(process, values);
        System.out.println("done");
        
        while(!trail.finished()) {
        	Thread.sleep(100);
        	System.out.print(trail.finished());
        }
        
        System.out.println(trail.showExecutionTrail());
        trail.createOntologyTrail().write(System.out, trail.getBaseURI());
        
        

        // get the output param using the index
        OWLIndividual outValue = values.getIndividualValue(process.getOutput());

        // display the result
        System.out.println("Book Price = ");
        System.out.println(Utils.formatRDF(outValue.toRDF()));
        System.out.println();
    }

    public static void main(String[] args) throws Exception {
        CreateSequence test = new CreateSequence();
        test.runTest();
    }
}
